#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <functional>
#include <unordered_map>
#include <sys/types.h> // 创建套接字
#include <sys/socket.h>
#include <netinet/in.h> // local的使用
#include <arpa/inet.h>  // htons inet_addr的使用
#include "Log.hpp"      // 日志的头文件

extern Log lg;

// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;

enum
{
    SOCKET_ERR = 1 // 创建套接字失败
    ,
    BIND_ERR = 2 // 绑定套接字失败
};

uint16_t defaultport = 8080;       // 端口的缺省地址
std::string defaultip = "0.0.0.0"; // ip的缺省地址
const int size = 1024;

class UdpServer
{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
        : port_(port), ip_(ip), isrunning_(false)
    {
    }

    void Init() // Udp初始化
    {
        // 1.创建 udp socket
        // 创建一个用于 IPv4 和 UDP 通信的套接字，将新创建的套接字的描述符存储在 sockfd 变量中
        // 2.Udp的socket是全双工的，允许被同时读写的
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0) // 如果创建失败，打印日志
        {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);

        // 2.绑定端口号  bind socket
        struct sockaddr_in local;     // 用于表示一个IPv4的网络地址，获取网络地址信息
        bzero(&local, sizeof(local)); // bzero 函数来将 local 结构体的所有字节设置为0，是一个好习惯
        local.sin_family = AF_INET;   // 设置地址族为IPv4
        // 需要保证我的端口号是网络字节序列，因为该端口号是要给对方发送的
        local.sin_port = htons(port_); // 设置端口号（注意：端口号需要以网络字节序给出，通常是通过htons函数转换）
        // 这里要将ip的 string 类型转换为 uint32_t 类型   ip必须是网络序列的
        local.sin_addr.s_addr = inet_addr(ip_.c_str()); // 设置IP地址（注意：IP地址需要是一个in_addr类型的值，通常通过inet_addr函数转换）
        // 网络通信的本质就是进程通信，我们使用端口号限定唯一的进程

        // 用于将套接字与特定的本地地址和端口号关联起来
        int n = bind(sockfd_, (const struct sockaddr *)&local, sizeof(local));
        if (n < 0) // 打印绑定失败日志
        {
            lg(Fatal, "bind error, error: %d,err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind sucess,errno: %d, err string: %s", errno, strerror(errno));
    }

    // 检查用户
    void CheckUser(const struct sockaddr_in &client, const std::string clientip, uint16_t clientport)
    {
        // uint16_t clientport = ntohs(client.sin_port);
        // std::string clientip = inet_ntoa(client.sin_addr);

        // 查找online_user_中是否有clientip，有的话插入
        auto iter = online_user_.find(clientip);
        if (iter == online_user_.end())
        {
            online_user_.insert({clientip, client});
            std::cout << "[" << clientip << ":" << clientport << "] add to online user." << std::endl;
        }
    }

    void Broadcast(const std::string &info, const std::string clientip, uint16_t clientport)
    {
        for (const auto &user : online_user_)
        {
            std::string message = "[";
            message += clientip;
            message += ":";
            message += std::to_string(clientport);
            message += "]# ";
            message += info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr *)(&user.second), len);
        }
    }

    // void Run(func_t func) // 对代码进行分层 Udp运行
    void Run()
    {
        isrunning_ = true; // 服务器正在运行

        char inbuffer[size]; // 缓冲区
        while (isrunning_)
        {
            struct sockaddr_in client;      // 客户端的网络地址
            socklen_t len = sizeof(client); // 存储client结构体大小的变量，用于recvfrom函数
            // recvfrom函数用于从UDP套接字接收数据
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);
            if (n < 0) // 打印警告日志
            {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }

            // 拿到了客户端对应的端口号，网络序列
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr); // 客户端的ip地址

            CheckUser(client, clientip, clientport);

            std::string info = inbuffer;
            Broadcast(info, clientip, clientport); // 广播所有人

            // inbuffer[n] = 0;
            //  充当了一次数据的处理
            // std::string info = inbuffer;
            //  std::string echo_string = "server echo# " + info;

            // std::string echo_string = func(info,clientip,clientport);

            // 使用sendto函数将echo_string发送回客户端，sendto函数用于UDP套接字，因为它允许您指定目标地址
            // sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr *)&client, len);
        }
    }

    ~UdpServer()
    {
        if (sockfd_ > 0)
            close(sockfd_);
    }

private:
    int sockfd_;     // 网络文件描述符
    std::string ip_; // 任意ip地址
    uint16_t port_;  // 端口号
    bool isrunning_; // 服务器是否正在运行
    std::unordered_map<std::string, struct sockaddr_in> online_user_;
};